

	/************************************************************************************************

		@API
		Class: 		jsl.Data.XNode
	  inherits:	jsl.Node

		Methods:
	  	s		toXml						(options*)
			xn	importChildren	(xml, options*)

		Overriding:
	  	xn	type 						(type*)
	  	xn	value 					(value*)
	  	xn	name 						(name*)
	  	xn	attribute 			(id, value*)
	  	xn	attach 					(parent, ix*)

		Static:
			xn	createDocument	()
			xn	createDtd				(name, content*)
			xn	createElement		(name, attributes*)
			xn	createText			(text*)
			xn	createComment		(comment*)
			xn	createPi				(name, content*)
			xn	createCdata			(content*)

		Note:
			The static method createEntity doesn't exists.
			We can create an entity putting &name; inside a text
			or inside an attribute value.

		Note:
			w3c xml node types					XNode types
			 1 	element										element
			 2 	attribute									--
			 3 	text											text
			 4 	cdata											cdata
			 5 	entity reference					entity
			 6 	entity										--
			 7 	p-instruction							pi
			 8 	comment										comment
			 9 	document									document
			 10 document type							document type
			 11 document fragment					--
			 12 notation									--

	************************************************************************************************/

	jsl.Class("jsl.Data.XNode", {

		inherits: jsl.Node,

		//static redefinitions:
		$__qts: jsl.Object.create(jsl.Node.__qts),
		$__qfs: jsl.Object.create(jsl.Node.__qfs),
		$__qvs:	jsl.Object.create(jsl.Node.__qvs),

		//-------------------------------------------------------------------------------
		initializer: function() {
			var re = ":A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD"

			re												= RegExp("^[" + re + "]([" + re + "\\-\\.0-9\\u00b7\\u0300-\\u036f\\u203f-\\u2040])*$")
			this.__re_name 						= re
			this.prototype.__re_name	= re
		},
		//static methods:
		//-------------------------------------------------------------------------------
		$createDocument: function() {
			var xn//!
			if(arguments.length)	throw jsl.ParamError()//!

			xn 					= new this
			xn.__name		= "#document"
			xn.__value	= null
			xn.__type		= 9
			return xn
		},
		//-------------------------------------------------------------------------------
		$createDtd: function(name, content) {
			var xn//!
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			jsl.validate(name, "s+", "name")
			if(!this.__re_name.test(name)) 								throw jsl.ParamError("BadCharacter:name")
			if(arguments.length === 2)										jsl.validate(content, "s", "content")//!

			xn 					= new this(name)
			xn.__value	= content ? content + "" : ""
			xn.__type		= 10
			return xn
		},
		//-------------------------------------------------------------------------------
		$createElement: function(name, attributes) {
			var xn, atts, nss, pfx

			//!
			if(!arguments.length || arguments.length > 2) throw jsl.ParamError()
			jsl.validate(name, "s+", "name")
			if(!this.__re_name.test(name))	throw jsl.ParamError("BadCharacter:name")
			if(arguments.length === 2)			jsl.validate(attributes, "o", "attributes")//!

			xn 					= new this(name)
			xn.__value	= null
			xn.__type		= 1

			if(attributes) {

				atts 	= {}
				nss		= {}

				for(var id in attributes)
					if(attributes.hasOwnProperty(id)) {

						//!
						if(!this.__re_name.test(id))	throw jsl.ParamError("BadCharacter:attribute(" + id + ")")//!
						pfx = id.slice(0, 6)
						if(pfx === "xmlns")				nss[""]						= attributes[id]
						else if(pfx === "xmlns:")	nss[id.slice(6)] 	= attributes[id]
						else											atts[id]					= attributes[id]
					}

				xn.__nss	= nss
				xn.__atts = atts
			}
			return xn
		},
		//-------------------------------------------------------------------------------
		$createText: function(text) {
			var xn//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			jsl.validate(text, "s", "text")//!

			xn 					= new this
			xn.__name		= "#text"
			xn.__value	= text ? text + "" : ""
			xn.__type		= 3
			return xn
		},
		//-------------------------------------------------------------------------------
		$createComment: function(comment) {
			var xn//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			jsl.validate(comment, "s", "comment")//!

			xn 					= new this
			xn.__name		= "#comment"
			xn.__value	= comment ? comment + "" : ""
			xn.__type		= 8
			return xn
		},
		//-------------------------------------------------------------------------------
		$createPi: function(name, content) {
			var xn//!
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			jsl.validate(name, "s+", "name")
			if(!this.__re_name.test(name)) 	throw jsl.ParamError("BadCharacter:name")
			if(/^xml$/i.test(name))					throw jsl.ParamError("ValueNotPermitted:name=xml")
			if(arguments.length === 2)			jsl.validate(content, "s", "content")//!

			xn 					= new this(name)
			xn.__value	= content ? content + "" : ""
			xn.__type		= 7
			return xn
		},
		//-------------------------------------------------------------------------------
		$createCdata: function(content) {
			var xn//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			jsl.validate(content, "s", "content")//!

			xn 					= new this
			xn.__name		= "#cdata-section"
			xn.__value	= content ? content + "" : ""
			xn.__type		= 4
			return xn
		},
		//-------------------------------------------------------------------------------
		XNode: function(name) {//!
			if(arguments.length > 1)	throw jsl.ParamError()//!

			this.__id = this.__proto.__id++

			if(arguments.length) {//!
				jsl.validate(name, "s+", "name")
				if(!this.__re_name.test(name)) throw jsl.ParamError("BadCharacter:name")//!
				this.name(name)
				this.__type = 1
			}
		},
		//-------------------------------------------------------------------------------
		toXml: function(options) {
			try 			{ return jsl.Data.toXml(this, options || {}) 									}
			catch(e) 	{ throw e instanceof jsl.ParamError ? e.clearStackTrace() : e }
		},
		//-------------------------------------------------------------------------------
		importChildren: function(xml, options) {
			var xn//!
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			jsl.validate(xml, "s+", "xml")
			if(arguments.length === 2) jsl.validate(options, "o", "options")//!

			xn 							= jsl.Data.fromXml("<d>" + xml + "</d>", options || {})
			this.__childs 	= xn.__drv_children()
			this.__drv_re		= xn.__drv_re
			this.__drv_tc		= xn.__drv_tc
			this.__drv_emap	= xn.__drv_emap

			return this
		},
		//Overriding
		//-------------------------------------------------------------------------------
		type: function(type) {//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			jsl.validate(type, "i[1,10]", "type")
			if(arguments.length && type == 2 || type == 6)	throw jsl.ParamError("BadValue")//!

			return jsl.Node.prototype.type.apply(this, arguments)
		},
		//-------------------------------------------------------------------------------
		name: function(name) {//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			jsl.validate(name, "s+", "name")
			if(arguments.length && !this.__re_name.test(name)) throw jsl.ParamError("BadCharacter:name")//!

			return jsl.Node.prototype.name.apply(this, arguments)
		},
		//-------------------------------------------------------------------------------
		value: function(value) {//!
			if(arguments.length > 1)	throw jsl.ParamError()//!
			return jsl.Node.prototype.value.apply(this, arguments)
		},
		//-------------------------------------------------------------------------------
		attribute: function(id, value) {//!
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			if(arguments.length)	jsl.validate(id, "s+", "id")
			if(!this.__re_name.test(id)) throw jsl.ParamError("BadCharacter:id")//!

			return jsl.Node.prototype.attribute.apply(this, arguments)
		},
		//-------------------------------------------------------------------------------
		attach: function(parent, ix) {
			var type//!
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			if(parent == null)										throw jsl.ParamError("NullValue:	parent")
			if(!jsl.Data.XNode.isClassOf(parent))	throw jsl.ParamError("BadType:		parent:jsl.Data.XNode")
			if(this.__type == null)								throw jsl.ParamError("OperationDenied:node(type=null)")

			//no controls are executed when a type is changed inside a node already present in a tree
			//in this case if we want perform such type of task we are responsable to write the right code
			//entity nodes cannot have children manually attached. Only the parser can.
			type = parent.__type
			if(type !== 1 && type !== 9)	throw jsl.ParamError("OperationDenied:parent(type)")
			if(this.__type === 9)					throw jsl.ParamError("OperationDenied:document")//!

			return jsl.Node.prototype.attach.apply(this, arguments)
		}
	})
	//-------------------------------------------------------------------------------
	jsl.Data.XNode.qtype("tag"			, 1)
	jsl.Data.XNode.qtype("text"			, 3)
	jsl.Data.XNode.qtype("cdata"		, 4)
	jsl.Data.XNode.qtype("pi"				, 7)
	jsl.Data.XNode.qtype("comment"	, 8)
	jsl.Data.XNode.qtype("dtd"			, 10)
	jsl.Data.XNode.qtype("entity"		, 5)
	jsl.Data.XNode.qtype("default"	, "tag")

	/************************************************************************************************

		Driver:
		---------------------------------------------------------------
		atts	__drv_attributes			()
		nss		__drv_nss							()
		c			__drv_child 					(ix)
		cs		__drv_children				()

	************************************************************************************************/

	//-------------------------------------------------------------------------------
	jsl.Data.XNode.prototype.__drv_re2 = /^<([^"'>]+|"[^"]*"|'[^']*')+>|<[^>]+>$/g
	jsl.Data.XNode.prototype.__drv_re3 = /^<([^\n\r\s>\/]+)/
	jsl.Data.XNode.prototype.__drv_re4 = /^<\?([^\s\n\r?]+)(?:[\s\n\r]([\s\S]*?))?\?>/
	jsl.Data.XNode.prototype.__drv_re5 = /^<!DOCTYPE[\s\r\n]([^\s\r\n]+)[\s\r\n]+(<!--[\s\S]*?-->|<\?[\s\S]*?\?>|[^"'\[>]+|"[^"]*"|'[^']*'|\[[^\]]*])+>/
	jsl.Data.XNode.prototype.__drv_re6 = /^<([^"'>]+|"[^"]*"|'[^']*')+>/
	jsl.Data.XNode.prototype.__drv_re7 = /[^\s\n\r="']+(?=[\s\n\r]*=)|"[^"]*"|'[^']*'/g

	//-------------------------------------------------------------------------------
	jsl.Data.XNode.prototype.__drv_re8 = /&#(x?\d+);|(&lt;)|(&gt;)|(&amp;)|(&apos;)|(&quot;)|&([^;'"<>]+);|^.|.$/g
	jsl.Data.XNode.prototype.__drv_f 	 = function(token, g1, g2, g3, g4, g5, g6, g7) {

		if(g1)																return String.fromCharCode("0" + g1)
		else if(g2) 													return "<"
		else if(g3) 													return ">"
		else if(g4) 													return "&"
		else if(g5) 													return "'"
		else if(g6)														return "\""
		else if(g7 && !arguments.callee.emap)	return token
		else if(g7 && arguments.callee.a)			return ("." + jsl.Data.XNode.__drv_en_resolve(g7, arguments.callee.emap) + ".").replace(jsl.Data.XNode.prototype.__drv_re8, arguments.callee)
		else if(g7)														return jsl.Data.XNode.__drv_en_resolve(g7, arguments.callee.emap)
		else																	return arguments.callee.l ? "" : token
	}
	jsl.Data.XNode.__drv_en_store 	= {}
	jsl.Data.XNode.__drv_en_resolve = function(id, map) {
		var st, v, f

		st = jsl.Data.XNode.__drv_en_store

		if(!st[id]) {
			f	= arguments.callee
			v	= map[id]
			//!
			if(v == null) throw jsl.ParamError("xml.EntityError", [ id ])//!
			st[id] = v.replace(/&(?!lt|gt|amp|apos|quot|#x?\d+)([^;"'<>]+);/g, function(txt, g1) { return f(g1, map) })
		}

		return st[id]
	}
	//-------------------------------------------------------------------------------
	jsl.Data.XNode.prototype.__drv_attributes 	=
	jsl.Data.XNode.prototype.__drv_nss					= function() {
		try {
			var as, ns, txt, tks, re, f, id

			as = {}
			ns = {}

			if(this.__type === 1) {

				txt = this.__drv_txt

				//nodes not created by fromXml
				if(txt) {

					txt	= txt.match(this.__drv_re6)[0]
					tks = txt.match(this.__drv_re7)

					if(tks) {

						re 			= this.__drv_re8
						f				= this.__drv_f
						f.emap 	= this.__drv_emap
						f.l			= f.a = true

						for(var a = 0, l = tks.length; a < l; a += 2) {
							id = tks[a].slice(0, 6)
							if(id === "xmlns")				ns[""]							= tks[a + 1].replace(re, f)
							else if(id === "xmlns:")	ns[tks[a].slice(6)] = tks[a + 1].replace(re, f)
							else											as[tks[a]] 					= tks[a + 1].replace(re, f)
						}
					}
				}
			}

			this.__atts = as
			this.__nss	= ns
			return
		}
		catch(e) {
			jsl.throwError(jsl.ParamError("SyntaxError:xml", e), this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Data.XNode.prototype.__drv_children = function() {
		try {
			var txt, cs, i, s

			if(this.__drv_txt && (this.__type === 1 || this.__type === 9)) {
				txt	= this.__drv_txt.replace(this.__drv_re2, "")
				if(txt) {

					//@sys 	Android-browser 4.0 - Occasionally one or more elements of the array "cs"
					//			are null (at random time). It is dependent from the length of the string
					//			used from the regexp. exec solve the problem
					if(jsl.__sys === "Android-browser")	{

						this.__drv_re.lastIndex = 0

						cs 	= []
						i		= 0

						while((s = this.__drv_re.exec(txt)))
							cs[i++] = s[0]
					}
					else
						cs = txt.match(this.__drv_re)
				}
			}

			return this.__childs = cs || []
		}
		catch(e) {
			jsl.throwError(jsl.ParamError("SyntaxError:xml", e), this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Data.XNode.prototype.__drv_child = function(ix) {
		try {
			var txt, name, type, value, f, e, xn, data, c, i, cs, ncs, map, c0

			txt		= this.__childs[ix]
			type 	= txt.slice(0, 3).split("")

			//1 - text
			//-------------------------------------------------------------------------------
			if(type[0] !== "<")

				//1.1 - pure text
				if(!this.__drv_emap || type[0] !== "&") {
					name 		= "#text"
					f				= this.__drv_f
					f.emap	= this.__drv_emap
					f.l			= f.a = false
					value		= txt.replace(this.__drv_re8, f)
					type		=	3
				}
				//1.2 - entity
				else {
					name  = txt.slice(1, -1)
					value	= this.__drv_emap[name]
					type	= 5

					//!
					if(value == null) throw jsl.ParamError("EntityError", [ name ])//!
					xn = jsl.Data.fromXml(this.__drv_emap["@dtd"] + "<doc>" + value + "</doc>", this.__drv_emap["@os"])
				}
			//2 - element
			//-------------------------------------------------------------------------------
			else if(type[1] !== "!" && type[1] !== "?") {
				name 	= txt.match(this.__drv_re3)[1].split("@")[0]
				value	= null
				type	=	1
				e			= true
			}
			//3 - comment
			//-------------------------------------------------------------------------------
			else if(type[2] === "-") {
				name 	= "#comment"
				value	= txt.slice(4, -3)
				type	=	8
			}
			//4 - processing-instruction
			//-------------------------------------------------------------------------------
			else if(type[1] === "?") {
				data	= txt.match(this.__drv_re4)
				name 	= data[1]
				value	= data[2] || ""
				type	=	7
			}
			//5 - cdata
			//-------------------------------------------------------------------------------
			else if(type[2] === "[") {
				name 	= "#cdata-section"
				value	= txt.slice(9, -3)
				type	=	4
			}
			//6 - document type
			//-------------------------------------------------------------------------------
			else if(type[2] === "D") {
				data	= txt.match(this.__drv_re5)
				name	= data[1]
				value	= data[2]
				type	=	10
			}

			//node creation
			//-------------------------------------------------------------------------------
			c 								= new jsl.Data.XNode
			i 								= name.indexOf(":")
			c.__name 					= name + ""
			c.__lname					= i >= 0 ? name.slice(i + 1) 	: c.__name
			c.__pfx						=	i >= 0 ? name.slice(0, i)		: null
			c.__type					= type
			c.__value 				= value
			c.__cix						= ix
			c.__drv_txt				= txt
			c.__drv_tc				= this.__drv_tc
			c.__drv_re				= this.__drv_re
			c.__drv_emap 			= this.__drv_emap
			c.__parent				= this
			this.__childs[ix]	= c

			if(type === 5) c.__childs = xn.__drv_children()

			//text nodes collapsing
			//-------------------------------------------------------------------------------
			if(e && this.__drv_tc) {

				cs 	= c.__drv_children()
				ncs	= []
				txt	= []
				map	= this.__drv_emap

				for(var a = 0, i1 = 0, i2 = 0, l = cs.length; a < l; a++) {

					c0 = cs[a].charAt(0)

					if(c0 === "<")							ncs[i2++]	= cs[a]
					else if(c0 === "&" && map) 	ncs[i2++] = cs[a]
					else 												txt[i1++] = cs[a]
				}

				c.__childs 	= ncs
				f						= this.__drv_f
				f.emap			= this.__drv_emap
				f.l					= f.a = false
				c.__value 	= txt.join("").replace(this.__drv_re8, f)
			}

			return c
		}
		catch(e) {
			jsl.throwError(jsl.ParamError("SyntaxError:xml", e), this, arguments)
		}
	}
